Docker Dockerfile
Un deuxième exemple avec Node.js
Docker est une plateforme qui va vous permettre d'exécuter votre code à l'intérieur d'un conteneur indépendamment de la machine sur laquelle vous êtes ! Un conteneur ressemble à une machine virtuelle sauf qu'il n'embarque pas tout un système d'exploitation avec lui ce qui lui permet de s'exécuter en quelque secondes et d'être beaucoup plus léger.
Docker peut donc résoudre notre problème d'environnement, car quelle que soit la machine que nous utiliserons, le code s'exécutera de la même manière.
La plateforme Docker est composée de deux éléments :
•.Le démon Docker qui s'exécute en arrière-plan et qui s'occupe de gérer vos conteneurs
•.Le client Docker qui vous permet d'interagir avec le démon par l'intermédiaire d'un outil en ligne de commande
Si vous voulez plus d'informations sur le fonctionnement interne de Docker je vous redirige vers l'article du site officiel : Understanding Docker
Le client Docker fonctionne sur tous les systèmes d'exploitation. En revanche, le démon Docker utilise des fonctionnalités du noyau Linux afin de gérer les conteneurs. Il ne fonctionne donc que sur Linux. Heureusement la majorité des serveurs utilise Linux, et pour vos ordinateurs sous OS X, ou même Windows, il existe une solution.
Si vous utilisez une machine avec une distribution Linux vous pourrez lancer le démon Docker directement sur cette dernière, par contre avec Windows ou OS X vous devrez lancer le démon dans une machine virtuelle, mais rassurez-vous, c'est très simple !
Pour le guide d'installation de Docker en fonction de votre système je vous redirige de nouveau vers le site officiel : Guide d'installation
Avant de commencer, vous allez devoir télécharger une image Docker qui servira de base à vos prochains conteneurs.
Pour cet exemple, on va partir d'une image Ubuntu :
$ docker pull ubuntu:trusty
trusty: Pulling from ubuntu
e9e06b06e14c: Pull complete
a82efea989f9: Pull complete
37bea4ee0c81: Pull complete
07f8e8c5e660: Already exists
ubuntu:trusty: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:014fa1d5b72b4fe0ec2b4642610fbbfdd52f502da8e14e80de07bd1dd774e4ef
Status: Downloaded newer image for ubuntu:trusty
Cette commande va télécharger depuis le Docker Hub l'image de la version 14.04 (trusty) d'Ubuntu. Il existe bien d'autres images que vous pourrez trouver sur le registry Docker.
Pour voir les images que vous avez téléchargées, utilisez cette commande :
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu trusty 07f8e8c5e660 4 weeks ago 188.3 MB
Sur ma machine, l'image d'Ubuntu fais 188.3 MB, je vous avais dit que c'était léger en comparaison à une machine virtuelle !
Maintenant, nous allons lancer un conteneur et rentrer à l'intérieur :
$ docker run -it ubuntu:trusty bash
root@2cdceb5ff771:/#
Cette commande crée un conteneur à partir de l'image ubuntu:trusty, y lance le programme bash et y attache votre shell grâce aux options -it
Vous pouvez maintenant exécuter les commandes que vous voulez, elle s'exécuteront à l'intérieur du conteneur, par exemple :
root@2cdceb5ff771:/#
$ apt-get moo
(__)
(oo)
/------\/
/ | ||
* /\---/\
~~ ~~
..."Have you mooed today?"...
Vous pouvez quitter le conteneur en faisant un Ctrl-d
Maintenant que vous êtes retourné sur votre machine, vous pouvez afficher la liste des conteneurs lancés avec cette commande :
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Il n'y a rien ? C'est normal ! En quittant le conteneur ce dernier s'est arrêté aussi. Pour l'afficher quand même, il suffit d'entrer cette commande :
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2cdceb5ff771 ubuntu:trusty "bash" 12 minutes ago Exited (0) 2 minutes ago loving_newton
Et pour supprimer ce conteneur ?
$ docker rm 2cdc
2cdc
Évidemment, remplacez '2cdc' par le CONTAINER ID approprié.
Passons maintenant à un deuxième exemple plus concret avec une application web.
Pour commencer, vous allez récupérer l'image docker officiel de Node.js en faisant :
$ docker pull node:0.12.4
0.12.4: Pulling from node
7711db4bb553: Pull complete
d1744e6e9471: Pull complete
9332645b03a3: Pull complete
a52a290821b3: Pull complete
3575f1347ce7: Already exists
39bb80489af7: Already exists
df2a0347c9d0: Already exists
7a3871ba15f8: Already exists
a2703ed272d7: Already exists
c9e3effdd23a: Already exists
node:0.12.4: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:81fb0812dd5e81f768773a121c8a6daced36893210c5ed50b504c4abcb04e10c
Status: Downloaded newer image for node:0.12.4
Puis créez un fichier server.js avec le contenu suivant :
var http = require("http");
var server = http.createServer(function(req, res) {
res.end("Coucou depuis Docker");
});
server.listen(3000);
Et maintenant, pour lancer notre application à l'intérieur d'un conteneur, vous devez faire :
$ docker run -d --name node-app -p 3000:3000 -v $(pwd):/app node:0.12.4 node /app/server.js
e9ca3cd8f90b8554ca99ec8ba15a039f827005bd8fecbf80d72ce7267006a6df
Si vous vous rendez sur localhost:3000 (ou l'IP de la VM si êtes sur Windows ou Mac), vous verrez : 'Coucou depuis Docker'
C'est beau, mais comment ça marche ? Examinons les options une par une :
•.-d : cette option permet de lancer le conteneur en mode démon et donc de tourner en tâche de fond à la différence de -it qui lançait le conteneur au premier plan et nous donnait un accès direct au conteneur.
•.--name node-app : cette option permet simplement de nommer notre conteneur, ce qui peut servir pour l'arrêter et le relancer plus simplement (et à d'autres choses plus complexes dont je parlerai dans un prochain article).
•.-p 3000:3000 : cette option permet de partager le port de votre machine avec le port du conteneur. Le premier nombre est le port de votre machine et le deuxième le port dans le conteneur.
•.-v $(pwd):/app : cette option permet de partager un dossier avec votre conteneur, ici, nous partageons le dossier courant (où se trouve notre fichier server.js) avec le dossier /app dans le conteneur (attention si vous êtes sur Mac ou Windows uniquement votre 'home' est partagé).
•.node:0.12.4 : l'image Docker que vous voulez utiliser.
•.node /app/server.js : la commande à exécuter dans le conteneur.
Et maintenant ? Vous pouvez afficher le conteneur en faisant : docker ps, l'arrêter avec : docker stop node-app et le supprimer avec docker rm node-app.
Les Dockerfiles sont des fichiers qui permettent de construire une image Docker adaptée à nos besoins, étape par étape. Rentrons dans le vif du sujet en créant une image permettant de lancer un projet JavaScript.
Pour commencer, créez un nouveau fichier Dockerfile à la racine de votre projet.
La première chose à faire dans un Dockerfile est de définir de quelle image vous héritez. Pour cet exemple, je vous propose d'utiliser une image de Debian comme base (ce qui est une bonne pratique, car cette image est plutôt légère en comparaison avec celle d'Ubuntu par exemple).
FROM debian:jessie
FROM permet de définir notre image de base, vous pouvez l'utiliser seulement une fois dans un Dockerfile.
Comme nous voulons créer une image pour une application JavaScript full-stack, nous devons commencer par installer Node.js. Pour ce faire, on va télécharger l'archive Node.js directement depuis le site officiel à l'aide de curl que nous allons aussi devoir installer.
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
RUN curl -LO "https://nodejs.org/dist/v0.12.5/node-v0.12.5-linux-x64.tar.gz" \
&& tar -xzf node-v0.12.5-linux-x64.tar.gz -C /usr/local --strip-components=1 \
&& rm node-v0.12.5-linux-x64.tar.gz
RUN permet d'exécuter une commande à l'intérieur de votre image comme si vous étiez devant un shell unix.
La première commande nous permet d'installer curl et de nettoyer ensuite le gestionnaire de paquets afin que notre image soit un peu plus légère.
Avec la deuxième commande, nous téléchargeons le binaire de Node.js que nous installons ensuite à sa place, et on n'oublie pas de supprimer l'archive ensuite.
Vous pouvez vous demander pourquoi j'exécute plusieurs commandes sur une même instruction RUN ? Eh bien, cela permet simplement de limiter le nombre d'instructions dans votre Dockerfile ce qui rendra votre image finale plus légère.
Maintenant, nous allons ajouter les sources de notre projet dans l'image et télécharger nos dépendances.
ADD package.json /app/
WORKDIR /app
RUN npm install
ADD . /app/
ADD permet d'ajouter des fichiers locaux ou distants à l'intérieur de votre image, il est le plus souvent utilisé pour importer les sources de votre projet ou des fichiers de configuration.
WORKDIR permet de changer le répertoire courant de votre image, toutes les commandes qui suivront seront exécutées à partir de ce répertoire.
Avec la dernière instruction, nous ajoutons les sources de notre projet à l'intérieur de l'image, mais vous allez vous demander pourquoi nous ne l'avons pas fait en même temps que l'ajout des fichiers de dépendances. Eh bien, cela nous permet d'économiser beaucoup de temps !
Quand Docker crée une nouvelle image à partir d'un Dockerfile, il exécute chaque instruction dans un conteneur, et le résultat de cette instruction est sauvegardé sous forme de couche. Au final, une image est un assemblage de plusieurs couches (une par instruction). Et donc, quand vous reconstruisez une image pour la seconde fois, les instructions qui n'impliquent pas de changements ne sont pas réexécutées, car la couche est récupérée depuis l'image précédente. Par contre, si l'instruction implique un changement quelconque, elle est réexécutée ainsi que toutes les instructions suivantes.
Dans notre cas, les sources auront tendance à beaucoup changer, et donc ne pas retélécharger les dépendances à chaque changement dans le code est un réel gain de temps !
Maintenant, nous allons indiquer quel port et dossier nous souhaitons partager avec l'extérieur du conteneur.
EXPOSE 3000
VOLUME /app/log
EXPOSE et VOLUME permettent respectivement d'indiquer quel port et quel dossier nous souhaitons partager.
Et pour finir, nous pouvons indiquer quelle instruction doit s'exécuter au lancement de votre conteneur grâce à l'instruction CMD.
CMD node server.js
Voici un résumé de notre Dockerfile :
# Image de base
FROM debian:jessie
# Installation de curl avec apt-get
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
# Installation de Node.js à partir du site officiel
RUN curl -LO "https://nodejs.org/dist/v0.12.5/node-v0.12.5-linux-x64.tar.gz" \
&& tar -xzf node-v0.12.5-linux-x64.tar.gz -C /usr/local --strip-components=1 \
&& rm node-v0.12.5-linux-x64.tar.gz
# Ajout du fichier de dépendances package.json
ADD package.json /app/
# Changement du repertoire courant
WORKDIR /app
# Installation des dépendances
RUN npm install
# Ajout des sources
ADD . /app/
# On expose le port 3000
EXPOSE 3000
# On partage un dossier de log
VOLUME /app/log
# On lance le serveur quand on démarre le conteneur
CMD node server.js
Avant de transformer ce Dockerfile en une image, vous devez créer un fichier de plus, le .dockerignore, ce fichier permet comme un .gitignore de ne pas inclure certain fichiers dans votre image Docker, et c'est très important afin d'éviter d'inclure les dépendances de votre projet dans votre image (node_modules dans notre cas) qui sont propres à votre système, mais pas au système du conteneur. Voici à quoi votre .dockerignore doit ressembler :
node_modules
.git
Pour transformer ce Dockerfile en une image Docker, vous devez utiliser cette commande :
$ docker build -t fullstack-js .
Sending build context to Docker daemon 4.381 MB
Sending build context to Docker daemon
Step 0 : FROM debian:jessie
---> bf84c1d84a8f
Step 1 : RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
---> Running in 93258459a279
...
---> 4fffcf3749a2
Removing intermediate container 93258459a279
Step 2 : RUN curl -LO "https://nodejs.org/dist/v0.12.5/node-v0.12.5-linux-x64.tar.gz" && tar -xzf node-v0.12.5-linux-x64.tar.gz -C /usr/local --strip-components=1 && rm node-v0.12.5-linux-x64.tar.gz
---> Running in a3a17d584bae
...
---> 4eaa62ace8de
Removing intermediate container a3a17d584bae
Step 3 : ADD *.json /app/
---> 1e8ffd7e10a8
Removing intermediate container 5db20e8b8ed2
Step 4 : WORKDIR /app
---> Running in 7b84b06642b1
---> 9c0e2287c34d
Removing intermediate container 7b84b06642b1
Step 5 : RUN npm install
---> Running in 0523df6e9aac
...
---> 6d7327ebee30
Removing intermediate container 0523df6e9aac
Step 6 : ADD . /app
---> 13bdbe70c6fa
Removing intermediate container 3c83d82c1d53
Step 7 : EXPOSE 3000
---> Running in 51e252173b12
---> 6c62eb1197e2
Removing intermediate container 51e252173b12
Step 8 : VOLUME /app/log
---> Running in 4af0bb73307b
---> 15b6190de473
Removing intermediate container 4af0bb73307b
Step 9 : CMD node server.js
---> Running in 9522c6b9bf95
---> aaf20fb25dac
Removing intermediate container 9522c6b9bf95
Successfully built aaf20fb25dac
L'option -t permet de nommer votre image docker, ce qui vous servira lorsque vous voudrez lancer votre conteneur. Et le . est le repertoire où se trouve le Dockerfile, dans notre cas le dossier courant.
Maintenant, vous pouvez lancer votre conteneur de cette manière :
$ docker run -d -p 3000:3000 -v $(pwd)/log:/app/log fullstack-js
Cette commande permet de lancer notre image en partageant le port et un dossier avec votre ordinateur, si vous voulez plus de détails sur le fonctionnement du client Docker, je vous invite à lire mon article précédent.
En cherchant sur Internet, vous pourrez trouver des images Docker pour tout et n'importe quoi, comme des images pour lancer Chrome dans un conteneur par exemple. Pour en savoir plus, je vous redirige vers le blog de Jessie Frazelle.